﻿using System;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using Perspective.Wpf3D.Primitives;
using Perspective.Core.Primitives;
using Perspective.Core.Wpf;
using System.Windows;
using System.Collections.Generic;

namespace Perspective.Wpf3D.Sculptors
{
    /// <summary>
    /// 组成平面正方形内切多边形的点和三角形集合
    /// A class to handle points and triangles of a 3D flat slice.
    /// </summary>
    // Contribution by Philippe Jovelin - 14/02/08 (ex- TruncatedPolygonSculptor)
    // Refactoring and "sector explosion" by Olivier Dewit - april-may 2008
    public class PolygonInSquareSculptor : Sculptor
    {
        #region Constant
        internal const double DefaultIncircleSideCount = 4 * 90;//需要与正方形内切 并且切点为内切圆上SideCount的点
        #endregion

        #region Members
        private int _initialAngle;
        private int _circumferenceSideCount;
        private double _circumcircleRadius;
        private Point3DCollection _squarePoints = new Point3DCollection();
        private Point3DCollection _incirclePoints = new Point3DCollection();
        private Point3DCollection _polygonPoints = new Point3DCollection();
        private List<int> _polygonPointIndex = new List<int>();
        private Point3D _center;
        private double _incircleSideCount = DefaultIncircleSideCount;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of PolygonInSquareSculptor.
        /// </summary>
        public PolygonInSquareSculptor()
        {

        }

        /// <summary>
        /// Initializes a new instance of PolygonInSquareSculptor.
        /// Vertices are automatically generated
        /// </summary>
        /// <param name="circumcircleRadius">Circumcircle Radius</param>
        /// <param name="circumferenceSideCount">Side count</param>
        /// <param name="initialAngle">Angle between the axis [origin - first point] and the X-axis, in degrees</param>
        public PolygonInSquareSculptor(double circumcircleRadius, int circumferenceSideCount, int initialAngle)
        {
            Initialize(circumcircleRadius, circumferenceSideCount, initialAngle);
        }

        /// <summary>
        /// Initializes an existing instance of PolygonInSquareSculptor.
        /// Vertices are automatically generated
        /// </summary>
        /// <param name="circumcircleRadius">Circumcircle Radius</param>
        /// <param name="circumferenceSideCount">Side count</param>
        /// <param name="initialAngle">Angle between the axis [origin - first point] and the X-axis, in degrees</param>
        public void Initialize(double circumcircleRadius, int circumferenceSideCount, int initialAngle)
        {
            _initialAngle = initialAngle;
            _circumferenceSideCount = circumferenceSideCount;
            _circumcircleRadius = circumcircleRadius;
            CreatePoints();
        }
        #endregion

        #region Properties
        /// <summary>
        /// The polygon's center point.
        /// </summary>
        public Point3D Center
        {
            get { return _center; }
            set { _center = value; }
        }

        /// <summary>
        /// 正方形点集合
        /// </summary>
        public Point3DCollection SquarePoints
        {
            get { return _squarePoints; }
        }

        /// <summary>
        /// 正方形内接圆点集合
        /// </summary>
        public Point3DCollection IncirclePoints
        {
            get { return _incirclePoints; }
        }

        /// <summary>
        /// 正方形内接多边形点集合
        /// </summary>
        public Point3DCollection PolygonPoints
        {
            get { return _polygonPoints; }
        }
        #endregion

        #region Functions
        /// <summary>
        /// 计算圆(半径为1)等分点的点位集合
        /// Create the points of the polygon.
        /// </summary>
        protected virtual void CreatePoints()
        {
            if (_circumferenceSideCount < 3)
            {
                throw new ArgumentException("_circumferenceSideCount < 3");
            }
            this.Points.Clear();
            _squarePoints.Clear();
            _incirclePoints.Clear();
            _polygonPoints.Clear();
            _polygonPointIndex.Clear();
            Point3D _p000 = new Point3D(-_circumcircleRadius, -_circumcircleRadius, 0);
            Point3D _p100 = new Point3D(_circumcircleRadius, -_circumcircleRadius, 0);
            Point3D _p110 = new Point3D(_circumcircleRadius, _circumcircleRadius, 0);
            Point3D _p010 = new Point3D(-_circumcircleRadius, _circumcircleRadius, 0);
            this.Points.Add(_p000);
            this.Points.Add(_p100);
            this.Points.Add(_p110);
            this.Points.Add(_p010);
            _squarePoints.Add(_p110);
            _squarePoints.Add(_p010);
            _squarePoints.Add(_p000);
            _squarePoints.Add(_p100);

            _center = new Point3D(0, 0, 0);
            double angle1 = 2 * Math.PI / _incircleSideCount;
            int interval = (int)(_incircleSideCount / _circumferenceSideCount);
            double angle = 0.0;
            for (int i = 0; i < _incircleSideCount; i++)
            {
                // angle = (angle1 * i);
                angle = angle1 * i;
                Point3D p = new Point3D();
                p.X = Math.Cos(angle) * _circumcircleRadius;
                p.Y = Math.Sin(angle) * _circumcircleRadius;
                p.Z = 0;
                _incirclePoints.Add(p);

                if (i < _initialAngle)
                {
                    if ((_initialAngle - i) % interval == 0)
                    {
                        _polygonPoints.Add(p);
                        _polygonPointIndex.Add(i);
                        this.Points.Add(p);
                    }
                }
                else if (i > _initialAngle)
                {
                    if ((i - _initialAngle) % interval == 0)
                    {
                        _polygonPoints.Add(p);
                        _polygonPointIndex.Add(i);
                        this.Points.Add(p);
                    }
                }
                else
                {
                    _polygonPoints.Add(p);
                    _polygonPointIndex.Add(i);
                    this.Points.Add(p);
                }
            }
        }

        protected override void CreateTriangles()
        {
            if (this.Points.Count == 0)
            {
                CreatePoints();
            }
            if ((Triangles.Count == 0) && (this.Points.Count > 0))
            {
                BuildTriangles();
            }
        }

        /// <summary>
        /// Build the triangles.
        /// </summary>
        public void BuildTriangles()
        {
            BuildTriangles(TriangleSideKind.Front);
        }

        /// <summary>
        /// Build the triangles, with a specific side orientation.
        /// </summary>
        /// <param name="tsk">TriangleSideKind value (triangle orientation).</param>
        public virtual void BuildTriangles(TriangleSideKind tsk)
        {
            this.Triangles.Clear();
            if (Points.Count < 3)
            {
                throw new ArgumentException("Points.Count < 3");
            }
            int interval = (int)(_incircleSideCount / _circumferenceSideCount);
            for (int i = 0; i < _incircleSideCount; i++)
            {
                Point3D squareVertex = _squarePoints[i / 90];
                if ((tsk & TriangleSideKind.Front) == TriangleSideKind.Front)
                {
                    if (i < _incircleSideCount - 1)
                    {
                        this.Triangles.Add(new Point3DTriplet(squareVertex, _incirclePoints[i + 1], _incirclePoints[i]));
                    }
                    else
                    {
                        this.Triangles.Add(new Point3DTriplet(squareVertex, _incirclePoints[0], _incirclePoints[i]));
                    }
                }
                if ((tsk & TriangleSideKind.Back) == TriangleSideKind.Back)
                {
                    if (i < _incircleSideCount - 1)
                    {
                        this.Triangles.Add(new Point3DTriplet(squareVertex, _incirclePoints[i], _incirclePoints[i + 1]));
                    }
                    else
                    {
                        this.Triangles.Add(new Point3DTriplet(squareVertex, _incirclePoints[i], _incirclePoints[0]));
                    }
                }
            }
            if (_circumferenceSideCount < DefaultIncircleSideCount)
            {
                for (int i = 0; i < _polygonPointIndex.Count; i++)
                {
                    if (i < _polygonPointIndex.Count - 1)
                    {
                        for (int j = _polygonPointIndex[i] + 1; j < _polygonPointIndex[i + 1]; j++)
                        {
                            if ((tsk & TriangleSideKind.Front) == TriangleSideKind.Front)
                            {
                                this.Triangles.Add(new Point3DTriplet(_polygonPoints[i], _incirclePoints[j], _incirclePoints[j + 1]));
                            }
                            if ((tsk & TriangleSideKind.Back) == TriangleSideKind.Back)
                            {
                                this.Triangles.Add(new Point3DTriplet(_polygonPoints[i], _incirclePoints[j + 1], _incirclePoints[j]));
                            }
                        }
                    }
                    else
                    {
                        for (int m = _polygonPointIndex[i] + 1; m < _incircleSideCount; m++)
                        {

                            if ((tsk & TriangleSideKind.Front) == TriangleSideKind.Front)
                            {
                                if (m < _incircleSideCount - 1)
                                {
                                    this.Triangles.Add(new Point3DTriplet(_polygonPoints[i], _incirclePoints[m], _incirclePoints[m + 1]));
                                }
                                else
                                {
                                    this.Triangles.Add(new Point3DTriplet(_polygonPoints[i], _incirclePoints[m], _incirclePoints[0]));
                                }
                            }
                            if ((tsk & TriangleSideKind.Back) == TriangleSideKind.Back)
                            {
                                if (m < _incircleSideCount - 1)
                                {
                                    this.Triangles.Add(new Point3DTriplet(_polygonPoints[i], _incirclePoints[m + 1], _incirclePoints[m]));
                                }
                                else
                                {
                                    this.Triangles.Add(new Point3DTriplet(_polygonPoints[i], _incirclePoints[0], _incirclePoints[m]));
                                }
                            }
                        }
                        if (_initialAngle > 0)
                        {
                            for (int k = 0; k < _polygonPointIndex[0]; k++)
                            {
                                if ((tsk & TriangleSideKind.Front) == TriangleSideKind.Front)
                                {
                                    this.Triangles.Add(new Point3DTriplet(_polygonPoints[i], _incirclePoints[k], _incirclePoints[k + 1]));
                                }
                                if ((tsk & TriangleSideKind.Back) == TriangleSideKind.Back)
                                {
                                    this.Triangles.Add(new Point3DTriplet(_polygonPoints[i], _incirclePoints[k + 1], _incirclePoints[k]));
                                }
                            }
                        }
                    }
                }
            }

        }

        /// <summary>
        /// A method for building the TextureCoordinates collection of the mesh.
        /// </summary>
        internal override void MapTexture()
        {
            foreach (Point3DTriplet pt in this.Triangles)
            {
                foreach (Point3D p in pt.Points)
                {
                    Mesh.TextureCoordinates.Add(
                        new Point(p.X, -p.Y));
                }
            }
        }
        #endregion
    }
}
